package com.android.test.net;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.zip.GZIPInputStream;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;

import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;

public class HttpManager {
    public static String HOST_URI = ""; // Here fill in the server address
    public static final String UPDATE_HOST_URI = "";// Here fill the update
						    // address
    public static final String UPDATE_URI = UPDATE_HOST_URI + "download.jsp";
    public static String DOWNLOAD_URI;
    public static String HOST_API_URI;
    public static String HOST_DOWNLOAD_URI;

    public static final int MSGCODE_HTTP_ERROR = 0;
    public static final int MSGCODE_HTTP_RESPONSE = 1;

    private static final int BUFSIZE = 1024;

    private static HttpManager httpManager;
    private Context mContext;

    private static final int STATUS_NORMAL = 0;
    private static final int STATUS_FAILED = 1;
    private static final int STATUS_RESUME = 2;

    private static final int RESUME_MESSAGE_ID = 1234;
    private static final int RESUME_TIME = 10000; // 10 second

    private static final int MAX_RETRY_TIMES = 5;

    private Queue<QueuedRequest> failedRequests = new LinkedList<QueuedRequest>();
    private Integer failedStatus = STATUS_NORMAL;

    private HttpCache httpCache;

    public static class QueuedRequest {
	public static final int requestTypeApi = 1;
	public static final int requestTypeImage = 2;
	public static final int requestTypeLog = 3;

	public int requestType;
	public int requestId;
	public int orgId;
	public String url;
	public List<NameValuePair> nameValuePairs;
	public Handler handler;
	public Object result;
	public int retry = MAX_RETRY_TIMES;

	// record log name
	public String logName;
	public byte[] logContent;
    }

    public static void initUrl() {
	DOWNLOAD_URI = HOST_URI + "d/";
	HOST_API_URI = HOST_URI + "api";
	HOST_DOWNLOAD_URI = HOST_URI + "d/";
    }

    private class Downloader extends HandlerThread {
	private int id;
	HttpClient httpClient;

	public Downloader(int id) {
	    super("worker" + id);
	    this.id = id;
	}

	public void init() {
	    httpClient = new IWHttpsClient(mContext);
	    threadHandlers[id] = new Handler(getLooper()) {
		public void handleMessage(Message msg) {

		    if (msg != null && msg.obj != null) {
			try {
			    doDownload((QueuedRequest) msg.obj);
			} catch (Exception e) {
			    e.printStackTrace();
			    return;
			}
		    }
		    // Clear the busy flag
		    availableWorkingThreadBits &= (~(1 << id));
		}
	    };
	}

	private void doDownload(QueuedRequest r) {
	    boolean error = false;

	    HttpUriRequest request = null;

	    // Log.d("HttpManager", "doDownload====r.url: " + r.url);
	    // Log.d("HttpManager", "r.nameValuePairs: " + r.nameValuePairs);
	    if (r.requestType == QueuedRequest.requestTypeApi) {
		HttpPost postRequest = new HttpPost(r.url);
		postRequest.addHeader("Accept-Encoding", "gzip");
		try {
		    postRequest.setEntity(new UrlEncodedFormEntity(
			    r.nameValuePairs, "utf-8"));
		} catch (UnsupportedEncodingException e) {
		    error = true;
		    // Log.e("HttpManager", e.getMessage(), e);
		}
		request = postRequest;
	    } else if (r.requestType == QueuedRequest.requestTypeLog) {
		HttpPost postRequest = new HttpPost(r.url);
		try {
		    postRequest.setEntity(new UrlEncodedFormEntity(
			    r.nameValuePairs, "utf-8"));
		} catch (UnsupportedEncodingException e) {
		    error = true;
		    // Log.e("HttpManager", e.getMessage(), e);
		}
		request = postRequest;
	    } else {
		request = new HttpGet(r.url);
	    }

	    if (error == false && r.requestType == QueuedRequest.requestTypeApi) {
		try {
		    StringBuffer sb = new StringBuffer();
		    HttpResponse response = httpClient.execute(request);
		    Header encodeHeader;
		    String encode = "";
		    if ((encodeHeader = response
			    .getFirstHeader("Content-Encoding")) != null) {
			encode = encodeHeader.getValue();
		    }

		    InputStreamReader reader;
		    if (encode.equals("gzip")) {
			reader = new InputStreamReader(new GZIPInputStream(
				response.getEntity().getContent()));
		    } else {
			reader = new InputStreamReader(response.getEntity()
				.getContent(), "utf-8");
		    }

		    char buffer[] = new char[BUFSIZE];
		    int count;
		    while ((count = reader.read(buffer, 0, buffer.length)) != -1) {
			sb.append(buffer, 0, count);
		    }
		    r.result = sb.toString();
		    reader.close();
		} catch (ClientProtocolException e) {
		    error = true;
		    // Log.e("HttpManager", e.getMessage(), e);
		} catch (IOException e) {
		    error = true;
		    // Log.e("HttpManager", e.getMessage(), e);
		}
	    } else if (error == false
		    && r.requestType == QueuedRequest.requestTypeLog) {
		try {
		    HttpResponse response = httpClient.execute(request);
		    r.result = 0;
		    if (response != null) {
			StatusLine status = response.getStatusLine();
			if (status != null) {
			    int code = status.getStatusCode();
			    if (code == 200) {
				r.result = 1;
			    }
			}
		    }
		} catch (ClientProtocolException e) {
		    error = true;
		    // Log.e("HttpManager", e.getMessage(), e);
		} catch (IOException e) {
		    error = true;
		    // Log.e("HttpManager", e.getMessage(), e);
		}
	    } else if (error == false
		    && r.requestType == QueuedRequest.requestTypeImage) {
		try {
		    r.result = httpCache.getCachedUrlContent(r.url, httpClient,
			    request);
		} catch (ClientProtocolException e) {
		    error = true;
		    // Log.e("HttpManager", e.getMessage(), e);
		} catch (IOException e) {
		    error = true;
		    // Log.e("HttpManager", e.getMessage(), e);
		}
	    } else {
		error = true;
		// Log.e("HttpManager", "Not defined requestType: " +
		// r.requestType);
	    }

	    // Try to send the success/failure message using handler
	    //
	    if (error == false) {
		// HTTP success response
		//
		if (r.handler == null) {
		    return;
		}
		r.handler.sendMessage(r.handler.obtainMessage(
			MSGCODE_HTTP_RESPONSE, r));
		return;
	    }

	    // Some problem is met in HTTP response, retry several times
	    if (r.retry > 0) {
		r.retry--;
		download(r, 1000);
		return;
	    }

	    if (r.handler == null) {
		// No handler, don't change the status, just discard the
		// request.
		return;
	    }

	    synchronized (failedStatus) {
		if (failedStatus == STATUS_NORMAL) {
		    // Step into failed status, and give user a message
		    failedStatus = STATUS_FAILED;
		    failedRequests.add(r);
		    r.handler.sendMessage(r.handler.obtainMessage(
			    MSGCODE_HTTP_ERROR, 0));
		} else if (failedStatus == STATUS_FAILED) {
		    // Waiting for user interaction, put the failed request in
		    // queue.
		    failedRequests.add(r);
		} else {
		    // STATUS_RESUME, re-send the message after 3 sec.
		    download(r, 3000);
		}
	    }

	    return;
	}
    }

    private int availableWorkingThreadBits = 0;
    private final static int numWorkingThreads = 6;
    private final static int allWorkingThreadBits = 0x3f; // 6 threads

    private Handler mHandler;
    private Handler[] threadHandlers = new Handler[numWorkingThreads];

    private class Dispatcher extends HandlerThread {
	public Dispatcher() {
	    super("dispatcher");
	}

	public void init() {
	    mHandler = new Handler(getLooper()) {
		public void handleMessage(Message msg) {
		    processMessage(msg);
		}
	    };
	}

	private void processMessage(Message msg) {

	    if (msg.what == RESUME_MESSAGE_ID) {
		synchronized (failedStatus) {
		    failedStatus = STATUS_NORMAL;
		}
		return;
	    }

	    if (availableWorkingThreadBits != allWorkingThreadBits) {
		// There's available thread, find it
		int threadid = -1;
		for (int i = 0; i < numWorkingThreads; ++i) {
		    if ((availableWorkingThreadBits & (1 << i)) == 0) {
			threadid = i;
			break;
		    }
		}

		// Send the queued request to the worker thread.
		availableWorkingThreadBits |= 1 << threadid;

		Message msg2 = Message.obtain();
		msg2.obj = msg.obj;
		threadHandlers[threadid].sendMessage(msg2);
	    } else {
		Log.i("http manager",
			"No available worker thread, postpone the http request!");

		// Send a timer message to self 1 second later.
		Message msg2 = Message.obtain();
		msg2.obj = msg.obj;
		mHandler.sendMessageDelayed(msg2, 1000);

	    }
	}
    }

    private HttpManager(Context context) {
	mContext = context;
	initUrl();

	for (int i = 0; i < numWorkingThreads; ++i) {
	    Downloader worker = new Downloader(i);
	    worker.setDaemon(true);
	    worker.start();
	    worker.init();
	}

	Dispatcher managerThread = new Dispatcher();
	managerThread.setDaemon(true);
	managerThread.start();
	managerThread.init();

	httpCache = new HttpCache();
	return;
    }

    public static HttpManager getInstance(Context context) {
	synchronized (HttpManager.class) {
	    if (httpManager == null) {
		httpManager = new HttpManager(context);
	    }
	}
	return httpManager;
    }

    private void download(QueuedRequest qr) {
	Message msg = Message.obtain();
	msg.obj = qr;
	mHandler.sendMessage(msg);
    }

    private void download(QueuedRequest qr, int millisec) {
	Message msg = Message.obtain();
	msg.obj = qr;
	mHandler.sendMessageDelayed(msg, millisec);
    }

    public void addApiRequest(List<NameValuePair> nameValuePairs) {
	QueuedRequest qr = new QueuedRequest();
	qr.requestType = QueuedRequest.requestTypeApi;
	qr.url = HOST_API_URI;
	qr.nameValuePairs = nameValuePairs;

	download(qr);
	return;
    }

    public void addApiRequest(List<NameValuePair> nameValuePairs,
	    int requestId, Handler handler) {
	QueuedRequest qr = new QueuedRequest();
	qr.requestType = QueuedRequest.requestTypeApi;
	qr.requestId = requestId;
	qr.url = HOST_API_URI;
	qr.nameValuePairs = nameValuePairs;
	qr.handler = handler;

	download(qr);
	return;
    }

    public void addApiRequest(List<NameValuePair> nameValuePairs,
	    int requestId, int orgId, Handler handler) {
	QueuedRequest qr = new QueuedRequest();
	qr.requestType = QueuedRequest.requestTypeApi;
	qr.requestId = requestId;
	qr.orgId = orgId;
	qr.url = HOST_API_URI;
	qr.nameValuePairs = nameValuePairs;
	qr.handler = handler;

	download(qr);
	return;
    }

    public void addImageRequest(String url, int requestId, Handler handler) {
	QueuedRequest qr = new QueuedRequest();
	qr.requestType = QueuedRequest.requestTypeImage;
	qr.requestId = requestId;
	qr.url = url;
	qr.nameValuePairs = null;
	qr.handler = handler;

	download(qr);
	return;
    }

    public void addImageRequest(String url, int requestId, int orgId,
	    Handler handler) {
	QueuedRequest qr = new QueuedRequest();
	qr.requestType = QueuedRequest.requestTypeImage;
	qr.requestId = requestId;
	qr.orgId = orgId;
	qr.url = url;
	qr.nameValuePairs = null;
	qr.handler = handler;

	download(qr);
	return;
    }

    public void addLogRequest(List<NameValuePair> nameValuePairs, byte[] data,
	    String logName, Handler handler) {
	QueuedRequest qr = new QueuedRequest();
	qr.requestType = QueuedRequest.requestTypeLog;
	qr.url = HOST_API_URI;
	qr.nameValuePairs = nameValuePairs;
	qr.logContent = data;
	qr.logName = logName;
	qr.handler = handler;

	download(qr);
	return;
    }

    public void retryRequest(Integer id) {
	synchronized (failedStatus) {
	    QueuedRequest qr = null;
	    while ((qr = failedRequests.poll()) != null) {
		download(qr);
	    }
	    failedStatus = STATUS_RESUME;
	}

	Message msg = Message.obtain();
	msg.what = RESUME_MESSAGE_ID;
	msg.obj = null;
	mHandler.sendMessageDelayed(msg, RESUME_TIME);
    }
}
